/** ShadowTemplate.java.

	Purpose:
		
	Description:
		
	History:
		12:49:11 PM Aug 20, 2015, Created by chunfu

Copyright (C) 2014 Potix Corporation. All Rights Reserved.
 */
package org.zkoss.zuti.zul;

import java.io.Serializable;
import java.util.Map;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.WrongValueException;
import org.zkoss.zk.ui.ext.DynamicPropertied;

/**
 * A utility to let developers to apply shadow elements in Java class. It has the similar behavior with {@link Apply}, for
 * example, developers can specify the template({@link #setTemplate(String)}) or pass parameters({@link #setDynamicProperty(String, Object)}).
 *
 * <p>The difference is that developers must designate a boolean value, called <tt>autodrop</tt>, to indicate whether to
 * drop those rendered children or not. If true, every time user changed template or detach from the original host,
 * ShadowTemplate will {@link Apply#recreate()} or removed the children, otherwise, rendered children will be remained.</p>
 * <p>After instantiating ShadowTemplate instance and above configuration, developers can trigger {@link #apply(Component)} to compose
 * the specified template with shadow host passed as parameter. Note that, the passed host should be the same one if
 * <tt>autodrop</tt> is true, or pass null to detach the original host first.</p>
 *
 * @author chunfu
 * @since 8.0.0
 */
public class ShadowTemplate implements DynamicPropertied, Serializable {

	private static final long serialVersionUID = 3925348440528461544L;

	private boolean _autodrop;
	private Component _host;
	private ApplyEx _apply; //internal implementation

	/**
	 * Constructor needs a boolean value to indicate whether to detached all rendered children automatically or not when
	 * template or host is changed.
	 * @param autodrop a boolean value
	 */
	public ShadowTemplate(boolean autodrop) {
		_autodrop = autodrop;
		init();
	}

	private void init() {
		_apply = new ApplyEx();
	}

	/**
	 * Returns the template name
	 * <p> Default: empty string
	 */
	public String getTemplate() {
		return _apply.getTemplate();
	}

	/**
	 * Sets the template name, for more detail, please check out {@link Apply#setTemplate(String)}
	 */
	public void setTemplate(String template) {
		_apply.setTemplate(template);
	}

	/**
	 * Sets the template uri, for more detail, please check out {@link Apply#setTemplateURI(String)}
	 *
	 * @param templateURI the template URI
	 */
	public void setTemplateURI(String templateURI) {
		_apply.setTemplateURI(templateURI);
	}

	/**
	 * Gets the template uri.
	 * @return template uri
	 */
	public String getTemplateURI() {
		return _apply.getTemplateURI();
	}

	//-- DynamicPropertied --//
	public boolean hasDynamicProperty(String name) {
		return _apply.hasDynamicProperty(name);
	}

	public Object getDynamicProperty(String name) {
		return _apply.getDynamicProperty(name);
	}

	public Map<String, Object> getDynamicProperties() {
		return _apply.getDynamicProperties();
	}

	public void setDynamicProperty(String name, Object value) throws WrongValueException {
		_apply.setDynamicProperty(name, value);
	}

	/**
	 * Return the current shadow host.
	 * @return host component
	 */
	public Component getShadowHost() {
		return _host;
	}

	/**
	 * Compose the specified template with the given host.
	 * Notice that,
	 * <ul>
	 *     <li>
	 *         If autodrop(the boolean value passed when instantiation) is true, users should apply to the same host
	 *         every time, otherwise, apply null to detach the original host, and then apply to the new one.
	 *     </li>
	 *     <li>
	 *        If autodrop is false, don't have to consider this situation, users can apply to any host you want except null.
	 *     </li>
	 * </ul>
	 *
	 * @param host as known as shadow host mentioned in other shadow element
	 */
	public void apply(Component host) {
		if (_autodrop) {
			applyDropTrue(host);
		} else {
			applyDropFalse(host);
		}
	}

	private void applyDropTrue(Component host) {
		if (host == null) {
			//remove all rendered children
			Component firstInsertion = _apply.getFirstInsertion();
			if (firstInsertion != null) {
				Component lastInsertion = _apply.getLastInsertion();
				for (Component next = firstInsertion, end = lastInsertion.getNextSibling(); next != end;) {
					Component tmp = next.getNextSibling();
					next.detach();
					next = tmp;
				}
			}

			//detach from _host
			_apply.mergeSubTree();
			_apply.detach();
			_host = null;
			return;
		}
		if (_host != null) { //has set host before
			if (_host != host)
				throw new UiException("The shadow element cannot change its host, if existed. [" + this
						+ "], please apply with null first!.");
		} else { //set for the first time
			_host = host;
			_apply.setShadowHost(_host, null);
		}

		if (_apply.getAfterCompose()) {
			_apply.recreate();
		} else {
			_apply.afterCompose();
		}
	}

	private void applyDropFalse(Component host) {
		if (host == null) {
			throw new UiException("The shadow host cannot be null. [" + this + "].");
		}
		_host = host;
		_apply.setShadowHost(_host, null);
		if (_apply.getAfterCompose()) {
			_apply.recreate();
		} else {
			_apply.afterCompose();
		}
	}

	/**
	 * Internal implementation of Apply
	 */
	private class ApplyEx extends Apply {
		//allow more access
		public void mergeSubTree() {
			super.mergeSubTree();
		}

		@Override
		public boolean isDynamicValue() {
			return _autodrop;
		}

		public boolean getAfterCompose() {
			return _afterComposed;
		}
	}
}
